
#include "nane/SerializeSystem/cfg/CfgSerializeSystem.h"
#include "nane/SerializeSystem/cfg/CfgSerializationProtocol.h"
#include "nane/core/IFileSystem.h"
#include "nane/core/assert.h"
#include "nane/core/log.h"

#include <algorithm>
#include <cassert>
#include <cctype>
#include <string>

namespace nane
{
    ////////////////////////////////////////////////////////////////////////////////////////////////
    enum CfgParseState
    {
        CPS_UNDEFINED = 0,
        CPS_WAIT_INPUT,
        CPS_WAIT_CLOSE_TAG,
        CPS_WAIT_EQUAL,
        CPS_WAIT_OPEN_VALUE,
        CPS_WAIT_ENDLINE,
        CPS_COMMENT
    };
    ////////////////////////////////////////////////////////////////////////////////////////////////
    CfgSerializeSystem::CfgSerializeSystem()
    {

    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    CfgSerializeSystem::~CfgSerializeSystem()
    {

    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool CfgSerializeSystem::Initialize()
    {
        return true;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void CfgSerializeSystem::Shutdown()
    {

    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool CfgSerializeSystem::Deserialize( IInput* _input, ISerializationProtocol* _protocol, byte* _buffer, uint32 _bufferSize )
    {
        if( _input == NULL || _protocol == NULL || _buffer == NULL || _bufferSize == 0)
            return false;
		
        bool wereErrors = false;
        uint32 bufferReadBegin = 0;
        uint32 bufferReadEnd = 0;
        uint32 bufferReadSize = _bufferSize;
        uint32 bufferWaitInputPos = 0;
        uint32 bytesRead = 0;
        uint32 tagBeginPos = 0;
        uint32 tagEndPos = 0;
        uint32 valueBeginPos = 0;
        uint32 valueEndPos = 0;
        uint32 line = 0;
        CfgParseState parseState = CPS_UNDEFINED;
        do
        {
            bytesRead = _input->Read(&(_buffer[bufferReadBegin]), bufferReadSize);
            assert( bytesRead <= bufferReadSize );
            
            bufferReadEnd = bufferReadBegin + bytesRead;
            //check for UTF-8 BOM
            if( parseState == CPS_UNDEFINED )
            {
                if( (bufferReadEnd - bufferReadBegin) >= 3 )
                {
                    if( _buffer[bufferReadBegin + 0] == 0xEF && _buffer[bufferReadBegin + 1] == 0xBB && _buffer[bufferReadBegin + 2] == 0xBF)
                    {
                        bufferReadBegin += 3;
                    }
                }
                parseState = CPS_WAIT_INPUT;
            }

            for( uint32 i = bufferReadBegin; i < bufferReadEnd; ++i )
            {
                char currentChar = static_cast<char>(_buffer[i]);
                if( currentChar == '\n' )
                {
                    ++line;
                }
                switch( parseState )
                {
                    case CPS_UNDEFINED:
                        NANE_ASSERT_RV( false, "Invalid state", false );
                        break;
                    case CPS_WAIT_INPUT:
                        if( ::isspace(static_cast<int>(currentChar)) == 0 )
                        {
                            tagBeginPos = i;
                            if( currentChar == '[' )
                            {
                                parseState = CPS_WAIT_CLOSE_TAG;
                            }
                            else if( currentChar == '#' || currentChar == ';' )
                            {
                                parseState = CPS_COMMENT;
                            }
                            else
                            {
                                parseState = CPS_WAIT_EQUAL;
                            }
                        }
                        break;
                    case CPS_WAIT_CLOSE_TAG:
                        if( currentChar == '\n' )
                        {
                            _buffer[i] = 0;
                            LOG_ERROR("line %1: Close tag mismatched for '%2'") << line << reinterpret_cast<const char*>(&_buffer[tagBeginPos]);
                            wereErrors = true;
                            parseState = CPS_WAIT_INPUT;
                            bufferWaitInputPos = i;
                        }
                        else if( currentChar == ']' )
                        {
                            _buffer[i] = 0;
                            _protocol->Serialize(reinterpret_cast<const char*>(&_buffer[tagBeginPos+1]));
                            parseState = CPS_WAIT_INPUT;
                            bufferWaitInputPos = i;
                        }
                        break;
                    case CPS_WAIT_EQUAL:
                        if( currentChar == '\n' || currentChar == '#' || currentChar == ';' )
                        {
                            _buffer[i] = 0;
                            LOG_ERROR("line %1: '=' mismatched for '%2'") << line << reinterpret_cast<const char*>(&_buffer[tagBeginPos]);
                            wereErrors = true;
                            parseState = (currentChar == '\n' ? CPS_WAIT_INPUT : CPS_COMMENT);
                            bufferWaitInputPos = i;
                        }
                        else if( currentChar == '=' )
                        {
                            parseState = CPS_WAIT_OPEN_VALUE;
                        }
                        else if( ::isspace(static_cast<int>(currentChar)) == 0 )
                        {
                            tagEndPos = i;
                        }
                        break;
                    case CPS_WAIT_OPEN_VALUE:
                        if( currentChar == '\n' || currentChar == '#' || currentChar == ';' )
                        {
                            _buffer[tagEndPos+1] = 0;
                            _protocol->Serialize(reinterpret_cast<const char*>(&_buffer[tagBeginPos]), "");
                            parseState = (currentChar == '\n' ? CPS_WAIT_INPUT : CPS_COMMENT);
                            bufferWaitInputPos = i;
                        }
                        else if( ::isspace(static_cast<int>(currentChar)) == 0 )
                        {
                            valueBeginPos = valueEndPos = i;
                            parseState = CPS_WAIT_ENDLINE;
                        }
                        break;
                    case CPS_WAIT_ENDLINE:
                        if( currentChar == '\n' || currentChar == '#' || currentChar == ';' )
                        {
                            _buffer[tagEndPos+1] = 0;
                            _buffer[valueEndPos+1] = 0;
                            _protocol->Serialize(reinterpret_cast<const char*>(&_buffer[tagBeginPos]), reinterpret_cast<const char*>(&_buffer[valueBeginPos]));
                            parseState = (currentChar == '\n' ? CPS_WAIT_INPUT : CPS_COMMENT);
                            bufferWaitInputPos = i;
                        }
                        else if( ::isspace(static_cast<int>(currentChar)) == 0 )
                        {
                            valueEndPos = i;
                        }
                        break;
                    case CPS_COMMENT:
                        if( currentChar == '\n' )
                        {
                            parseState = CPS_WAIT_INPUT;
                            bufferWaitInputPos = i;
                        }
                        break;
                }   // switch
            } 
            
            if( parseState != CPS_WAIT_INPUT && parseState != CPS_COMMENT )
            {
                uint32 unprocessedSize = bufferReadEnd - bufferWaitInputPos;
                if( unprocessedSize == _bufferSize )
                {
                    LOG_ERROR("Buffer overflow while parsing line %1. Line would be skipped") << line;
                    wereErrors = true;
                    parseState = CPS_COMMENT;
                    bufferReadBegin = 0;
                    bufferReadSize = _bufferSize;
                }
                else
                {
                    std::memcpy(_buffer, &_buffer[bufferWaitInputPos], unprocessedSize);
                    tagBeginPos -= bufferWaitInputPos;
                    tagEndPos -= bufferWaitInputPos;
                    valueBeginPos -= bufferWaitInputPos;
                    valueEndPos -= bufferWaitInputPos;
                    bufferReadBegin = unprocessedSize;
                    bufferReadSize = _bufferSize - unprocessedSize;
                }
            }
            else
            {
                bufferReadBegin = 0;
                bufferReadSize = _bufferSize;
            }

        } while( bufferReadSize == bytesRead );

        _buffer[_bufferSize-1] = 0;
        if( parseState == CPS_WAIT_CLOSE_TAG || parseState == CPS_WAIT_EQUAL )
        {
            LOG_ERROR("Unexpected end of file. Unclosed tag '%1'") << reinterpret_cast<const char*>(&_buffer[tagBeginPos]);
            wereErrors = true;
        }
        else if( parseState == CPS_WAIT_OPEN_VALUE )
        {
            _buffer[tagEndPos+1] = 0;
            _protocol->Serialize(reinterpret_cast<const char*>(&_buffer[tagBeginPos]), "");
        }
        else if( parseState == CPS_WAIT_ENDLINE )
        {
            _buffer[tagEndPos+1] = 0;
            _buffer[valueEndPos+1] = 0;
            _protocol->Serialize(reinterpret_cast<const char*>(&_buffer[tagBeginPos]), reinterpret_cast<const char*>(&_buffer[valueBeginPos]));
        }
        return !wereErrors;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    ISerializationProtocol* CfgSerializeSystem::CreateProtocol( IOutputRef* _output )
    {
        if( _output == NULL )
            return NULL;
        return new CfgSerializationProtocol(OutputPtr(_output));
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void CfgSerializeSystem::ReleaseProtocol( ISerializationProtocol* _protocol )
    {
        delete static_cast<CfgSerializationProtocol*>(_protocol);
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
}   // namespace nane
